using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Castle.DynamicProxy;

namespace Framework.Core.Reflection
{
    internal class FilteredMethodFactory : IMethodFactory
    {
        private readonly IMethodFactory methodFactory;
        private readonly IEnumerable<Type> excludedTypes; 

        public FilteredMethodFactory(IMethodFactory methodFactory, IEnumerable<Type> excludedTypes)
        {
            this.methodFactory = methodFactory;
            this.excludedTypes = excludedTypes;
        }

        public IMethod Create<T>(Expression<T> expression)
        {
            return Filter(methodFactory.Create(expression));
        }

        private IMethod Filter(IMethod method)
        {
            return new Method(method.Assembly,
                method.Type,
                method.Name,
                method.Arguments.Select(a => Filter(excludedTypes, a)));
        }

        private static IArgument Filter(IEnumerable<Type> excludedTypes, IArgument argument)
        {
            return excludedTypes.Contains(argument.Type) ? new Argument(argument.Type, new IgnoredType()) : argument;
        }

        public IMethod Create(IInvocation invocation)
        {
            return Filter(methodFactory.Create(invocation));
        }
    }

    internal class MethodFactory : IMethodFactory
    {
        public IMethod Create<T>(Expression<T> expression)
        {
            var methodCallExpression = (MethodCallExpression)expression.Body;
            var parameters = GetLambdaArguments(methodCallExpression);
            var methodInfo = methodCallExpression.Method;

            return new Method(
                methodInfo.Module.Assembly.FullName,
                methodInfo.DeclaringType.FullName,
                methodInfo.Name,
                parameters.Select(ToArgument));
        }

        private static IEnumerable<LambdaExpression> GetLambdaArguments(MethodCallExpression methodCallExpression)
        {
            return methodCallExpression.Arguments.Select(a => Expression.Lambda(a)).ToList();
        }

        private static Argument ToArgument(LambdaExpression a)
        {
            var value = a.Compile().DynamicInvoke();

            return new Argument(a.ReturnType, value);
        }


        public IMethod Create(IInvocation invocation)
        {
            var methodInfo = invocation.Method;
            return new Method(
                methodInfo.Module.Assembly.FullName,
                methodInfo.DeclaringType.FullName,
                methodInfo.Name,
                GetArguments(invocation));
        }

        private static IEnumerable<IArgument> GetArguments(IInvocation invocation)
        {
            var parameterInfos = invocation.Method.GetParameters();
            for (var i = 0; i < parameterInfos.Count(); i++)
            {
                yield return new Argument(parameterInfos[i].ParameterType, invocation.Arguments[i]);
            }
        }
    }
}